package com.lecture.junit.tdd.example;


import java.util.Calendar;

import org.junit.Before;
import org.junit.Test;

import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.*;

public class LogonServiceTest {

    public static final String BRETT = "brett";
    public static final String PASSWORD = "password";
    IAccount account;
    IAccountRepository accountRepository;
    LoginService service;
    
    @Before
    public void init() {
        account = mock(IAccount.class);
        when(account.getUserName()).thenReturn(BRETT);
        accountRepository = mock(IAccountRepository.class);
        when(accountRepository.find(anyString())).thenReturn(account);
        TimeStamp currentDay = new TimeStamp(Calendar.YEAR, Calendar.DAY_OF_YEAR); 
        when(account.getTimeStamp()).thenReturn(currentDay);
        service = new LoginService(accountRepository);
    }

    public void willPasswordMatch(boolean flag) {
        when(account.passwordMatches(anyString())).thenReturn(flag);
    }

    @Test
    public void stestLogin_shouldLogin_whenUserNameAndPasswordPassed() {
        willPasswordMatch(true);
        service.login("brett", PASSWORD);
        verify(account, times(1)).setLoggedIn();
    }

    @Test
    public void testLogin_shouldRevokeAccount_whenThreeLoginAttemptsFail() {
        willPasswordMatch(false);

        service.login("brett", PASSWORD);
        service.login("brett", PASSWORD);
        service.login("brett", PASSWORD);

        verify(account).setRevoked();
    }

    @Test
    public void testLogin_shouldNotSetLoggedIn_whenPasswordNotMatches() {
        willPasswordMatch(false);
        service.login(BRETT, PASSWORD);

        verify(account, never()).setLoggedIn();
    }

    @Test
    public void testLogin_shouldNotRevoke_whenOneAccFailsTwoTimesAndOtherFailsOneTime() {
        willPasswordMatch(false);
        IAccount second = mock(IAccount.class);
        when(second.getUserName()).thenReturn("YURA");
        doReturn(false).when(second).passwordMatches(anyString());
        when(accountRepository.find("YURA")).thenReturn(second);

        service.login(BRETT, PASSWORD);
        service.login(BRETT, PASSWORD);

        service.login("YURA", PASSWORD);
        verify(second, never()).setRevoked();
    }

    @Test(expected = AccountAlreadyLoggedInException.class)
    public void testLogin_shouldNotifyWithException_whenLoggedIndAccTriesToLogin() {
        willPasswordMatch(true);
        doReturn(true).when(account).isLoggedIn();

        service.login(BRETT, PASSWORD);
    }

    @Test(expected = AccountNotFoundException.class)
    public void testLogin_shouldThrowException_whenAccIsNotFound() {
        doReturn(null).when(accountRepository).find(anyString());

        service.login(BRETT, PASSWORD);
    }

    @Test(expected = AccountIsRevokedException.class)
    public void testLogin_shouldNotLoginAndThrowException_whenAccRevoked() {
        doReturn(true).when(account).isRevoked();
        willPasswordMatch(true);
        service.login(BRETT, PASSWORD);

        verify(account, never()).setLoggedIn();
    }

    @Test
    public void testLogin_shouldRestBackToInitialState_afterSuccessfulLogin() {
        willPasswordMatch(false);
        service.login(BRETT, PASSWORD);
        service.login(BRETT, PASSWORD);
        willPasswordMatch(true);
        service.login(BRETT, PASSWORD);
        willPasswordMatch(false);
        service.login(BRETT, PASSWORD);

        verify(account, never()).setRevoked();
    }
    
    // My tests
    
/***
 *   Password expiration
 */
    
    @Test(expected = PasswordExpiredException.class)
    public void testLogin_shouldThrowExceptionAndNotLoggedIn_whenPasswordExpired() {
    	willPasswordMatch(true);
    	when( account.isPasswordExpired()).thenReturn(true);
    	try	{
    		service.login(BRETT, PASSWORD);
    	} finally{
    		verify(account, never()).setLoggedIn();  		
    	}
    }
    
    @Test
    public void testLogin_shouldLogin_AfterChangingPassword(){
    	willPasswordMatch(true);
    	when( account.isPasswordExpired()).thenReturn(true);
    	try {
    		service.login(BRETT, PASSWORD);
    	} catch(PasswordExpiredException e) {
    		
    	} finally {
    		when( account.isPasswordExpired()).thenReturn(false);
    		service.login(BRETT, "NEW_PASS");
    		verify(account).setLoggedIn(); 
    	}
    }
    
    @Test
    public void testLogin_shouldNotLogin_AfterSecondLoginTryWithoutChangingPassword(){
    	willPasswordMatch(true);
    	when( account.isPasswordExpired()).thenReturn(true);
    	try {
    		service.login(BRETT, PASSWORD);
    	} catch(PasswordExpiredException e) {
    		
    	}
    	
		try {
			service.login(BRETT, PASSWORD);
		} catch (PasswordExpiredException e){
			
		}
		
		verify(account, never()).setLoggedIn();
    }
   
/***
 * 
 * Recently used passwords
 * 
 * New methods:  getUsedPaswordsNumber();
 * 				 removeFirstUsedPassword();
 * 				 addUsedPassword(String password);
 * 				 containsUsedPassword(String password).
 * 
 * Three first methods must be used by ChangePasswordService, 
 * which is not implemented yet.
 */
    @Test(expected = PasswordRecentlyUsedException.class)
    public void testLogin_shouldThrowException_whenLoggingInWithRecentlyUsedPassword() {
    	willPasswordMatch(true);
    	when( account.containsUsedPassword(PASSWORD)).thenReturn(true);
    	try{
    		service.login(BRETT, PASSWORD);
    	} finally {    		
    		verify(account, never()).setLoggedIn();
    	}
    	
    }
    
/***
 *  Time stamp
 */
    @Test
    public void testLogin_shouldRevokeAccount_whenAccountLastUsedTooLongThisYear(){
    	willPasswordMatch(true);
    	TimeStamp today = account.getTimeStamp();
    	TimeStamp lastTimeStampDay = new TimeStamp(today.getYear(), 
    			today.getDayOfYear() - LoginServiceState.MAX_DAYS_SINCE_LAST_USE - 1); 
        when(account.getTimeStamp()).thenReturn(lastTimeStampDay);
        
        service.login(BRETT, PASSWORD);
        verify(account).setRevoked();
    }
    
    @Test
    public void testLogin_shouldRevokeAccount_whenAccountLastUsedTooLongLastYear(){
    	willPasswordMatch(true);
    	TimeStamp today = account.getTimeStamp();
    	TimeStamp lastTimeStampDay = new TimeStamp(today.getYear()-1, 0); 
        when(account.getTimeStamp()).thenReturn(lastTimeStampDay);
        
        service.login(BRETT, PASSWORD);
        verify(account).setRevoked();
    }
}
